S14-01 SSR-Vue3 SSR
[TOC]
概念
SEO
概述
SEO(Search Engine Optimization,搜索引擎优化)是通过优化网站结构、内容和外部链接,提高网站在搜索引擎结果页(SERP)中的排名,从而增加网站流量的技术和策略。
优化策略:
语义性HTML标记:
标题用
<h1>
,一个页面只有一个; 副标题用<h2>
到<h6>
。不要过度使用h标签,多次使用不会增加 SEO。段落用
<p>
。列表用
<ul>
,并且li只放在 ul 中。
每个页面需包含:标题+内部链接: 每个页面对应的title,同一网站所有页面都有内链可以指向首页。
确保链接可供抓取:
meta标签优化: 设置
description
keywords
等。文本标记和img:
- 比如使用
<b>
和<strong>
加粗文本的标签,爬虫也会关注到该内容。 - img标签添加
alt
属性,图片加载失败,爬虫会取alt内容。
- 比如使用
robots.txt文件: 规定爬虫可访问您网站上的哪些网址。robots文件生成
sitemap.xml站点地图: 在站点地图列出所有网页,确保爬虫不会漏掉某些网页。
更多:https://developers.google.com/search/docs/crawling-indexing/valid-page-metadata
爬虫-工作流程
Google爬虫的工作流程分为3个阶段,并非每个网页都会经历这3个阶段:
- 抓取网页: 爬虫(蜘蛛),从互联网上发现各类网页,网页中的外部连接也会被发现。
- 索引编制: 爬虫程序会分析网页上的文本、图片和视频文件,并将信息存储在大型数据库(索引区)中。爬虫会对内容类似的网页归类分组,不符合规则内容和网站会被清理,如禁止访问或需要权限的网站等。
- 搜索结果: 当用户在Google中搜索时,搜索引擎会根据内容类型,选择一组网页中最具代表性的网页进行呈现。
CSR
概述
CSR(Client-Side Rendering,客户端渲染)是一种网页渲染方式,主要依靠浏览器在用户设备上生成HTML内容。应用程序的JS代码在客户端执行,从而动态生成页面。
客户端渲染原理:
SPA
SPA(Single Page Application,单页面应用)是一种Web应用架构,属于CSR,它通过在单个HTML页面上动态加载内容,实现流畅的用户体验。与传统的多页面应用不同,SPA在用户与应用交互时,只更新部分内容,而无需重新加载整个页面。
特点:
- SPA属于CSR(Client-Side Rendering,客户端渲染)
- SPA应用默认只返回一个空HTML页面,如
<div id="app"></div>
。 - 整个应用程序的内容都是通过 Javascript 动态加载,包括应用程序的逻辑、UI 以及与服务器通信相关的所有数据。
- 构建 SPA 应用常见的库和框架有: React、AngularJS、Vue.js 等。
SPA优点:
- 流畅性:用户体验更佳,页面切换更快。
- 减轻服务器负担:只请求必要的数据,而非整个页面。
- 前后端分离:可以更容易地进行前后端开发协作。
SPA缺点:
- 不利于SEO:由于内容是在客户端渲染的,搜索引擎可能难以索引。
- 首屏加载时间:可能比多页面应用稍慢,因为需要下载和解析JavaScript。
- 浏览器历史管理:需要处理浏览器的前进和后退按钮。
- 不利于构建复杂项目: 复杂web应用的大文件难以维护。
SSG
SSG(Static Site Generator,静态站点生成器)是一种工具,用于根据预定义的模板和内容生成静态HTML文件的过程。常用于构建网站、博客或文档。
特点:
- SSG 应用一般在构建阶段就确定了网站的内容。
- 如果网站的内容需要更新了,那必须得重新再次构建和部署。
- 构建 SSG 应用常见的库和框架有: Vue Nuxt、 React Next.js 等。
SSG优点:
- 性能优越: 访问速度非常快,因为每个页面都是在构建阶段就已经提前生成好了。
- SEO友好: 直接给浏览器返回静态的HTML,也有利于SEO。
- 保留SPA特性: SSG应用依然保留了SPA应用的特性,比如:前端路由、响应式数据、虚拟DOM等。
SSG缺点:
- 动态内容限制:页面都是静态,不利于展示实时性的内容,实时性的更适合SSR。
- 更新复杂性:如果站点内容更新了,那必须得重新再次构建和部署。
- 构建时间:对于大型网站,生成所有静态页面可能需要较长时间。
SSR
概述
SSR(Server-Side Rendering,服务器端渲染)是一种用于提高Web应用性能和SEO优化的技术。SSR在服务器上生成HTML页面并将其发送到客户端。
特点:
- SSR应用的页面是在服务端渲染的,用户每请求一个SSR页面都会先在服务端进行渲染,然后将渲染好的页面,返回给浏览器呈现。
- 构建 SSR 应用常见的库和框架有: Vue Nuxt、 React Next.js 等(SSR应用也称同构应用) 。
服务器端渲染原理:
1、通过node将vue项目打包成静态html页面
2、激活应用程序,实现交互(Hydration)
SSR优点:
更快的首屏渲染速度:
- 浏览器显示静态页面的内容要比 JavaScript 动态生成的内容快得多。
- 当用户访问首页时可立即返回静态页面内容,而不需要等待浏览器先加载完整个应用程序。
更好的SEO:
- 爬虫是最擅长爬取静态的HTML页面,服务器端直接返回一个静态的HTML给浏览器。
- 这样有利于爬虫快速抓取网页内容,并编入索引,有利于SEO。
保留Web交互性: SSR应用程序在 Hydration 之后依然可以保留 Web 应用程序的交互性。比如:前端路由、响应式数据、虚拟DOM等。
SSR缺点:
- 服务器负担:SSR需要对服务器进行更多 API 调用,在服务器端渲染需要消耗更多的服务器资源,成本高。
- 增加开发成本:用户需要关心哪些代码是运行在服务器端,哪些代码是运行在浏览器端。
- 复杂性:SSR 配置站点的缓存通常会比SPA站点要复杂一点。
SSR解决方案
SSR解决方案:
方案一:php、jsp等
方案二:从零搭建SSR 项目( Node+webpack+Vue/React )
方案三:直接使用流行的框架(推荐)
React : Next.js(127K)、Remix.js(29.8K)
Vue3 : Nuxt3(54.7K)
Vue2 : Nuxt.js
Angular : Anglular Universal
SSR应用场景:
- 性能要求高的系统:移动端、弱网环境
- 操作交互简单的系统:
- SaaS产品,如:电子邮件网站、在线游戏、客户关系管理系统(CRM)、采购系统等
- 门户网站、电子商务、零售网站
- 单个页面、静态网站、文档类网站
原生实现
API
vue
createSSRApp():
(rootComponent)
, Vue 3 中用于创建服务端渲染(SSR)应用的函数。它通常在 Vue 3 的 SSR 相关环境中使用,如@vue/server-renderer
。rootComponent:
Component
,应用的根组件,通常是一个 Vue 组件对象。通常为App
组件。返回:
app:
App
,SSR 应用实例。js// server.js import { createSSRApp } from 'vue'; import { renderToString } from '@vue/server-renderer'; import MyComponent from './MyComponent.vue'; async function createApp() { const app = createSSRApp(MyComponent); return app; } async function render() { const app = await createApp(); const html = await renderToString(app); return html; } render().then((html) => { console.log(html); // 输出生成的 HTML 字符串 });
@vue/server-renderer
renderToString():
(app)
,将 Vue 组件渲染为 HTML 字符串。app:
Component
,需要渲染的 Vue 应用实例,可以是一个组件或一个 Vue 应用。返回:
promise:
resovle,reject
,返回一个 Promise,解析为包含组件的 HTML 字符串。- resovle:
(htmlString) => void
,参数为html字符串。 - reject:
(err) => void
,
jsimport { createSSRApp } from 'vue'; import { renderToString } from '@vue/server-renderer'; import Greeting from './Greeting.vue'; const app = createSSRApp(Greeting, { name: 'World' }); // 使用 renderToString 渲染为 HTML 字符串 renderToString(app).then(htmlString => { console.log(htmlString); // 输出: <h1>Hello, World!</h1> }).catch(err => { console.error('渲染出错:', err); });
- resovle:
webpack-node-externals
webpack-node-externals:是一个 Webpack 插件,主要用于在打包 Node.js 应用时,自动排除 node_modules
目录中的所有依赖,以避免将它们打包到输出文件中。
注意:
- 对于服务器端代码(如 Express 应用)特别有用,因为 Node.js 会在运行时直接从
node_modules
中加载这些依赖,而不需要将它们打包。 - 对于在浏览器中使用的代码,需要在配置中指定不排除它们。
配置:
// webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node', // 指定打包目标为 Node.js
externals: [nodeExternals()], // 使用插件排除 node_modules
// 其他配置...
};
Vue3 SSR
Vue除了支持开发SPA应用之外,其实也是支持开发SSR应用的。
在Vue中创建SSR应用,需要调用createSSRApp函数,而不是createApp
- createApp:创建应用,直接挂载到页面上
- createSSRApp:创建应用,是在激活的模式下挂载应用
服务端用 @vue/server-renderer 包中的 renderToString 来进行渲染。
实现过程
实现思路:
- 1 开发一个App应用,比如App.vue。
- 2 将App.vue打包为一个服务器端的server_bundle.js文件,用来在服务器端动态生成页面的HTML。
- 3 将App.vue打包为一个客户端的client_bundle.js文件,用来激活应用,使页面有交互效果。
- 4 server_bundle.js 渲染的页面 + client_bundle.js 文件进行Hydration。
搭建Node Server
依赖包:
- express
- 安装:
pnpm i express
- 安装:
- nodemon:启动Node程序时并监听文件的变化,变化即刷新。
- 安装:
pnpm i nodemon -D
- 安装:
- webpack、webpack-cli
- 安装:
pnpm i webpack webpack-cli -D
- 安装:
- webpack-node-externals:打包时排除 node_modules 中所有的模块。
- 安装:
pnpm i webpack-node-externals -D
- 安装:
搭建过程:
1、运行pnpm init
初始化package.json
2、创建一个express服务器
3、在package.json
中编写npm脚本,运行node服务器
4、打包/src/server/index.js
文件
打包配置
/config/wk.config.js
。target打包命令,
--watch
表示内容变化时会重新打包。
5、优化打包:使用 webpack-node-externals 在打包时排除node_modules
中的包。
此时打包的js文件有900kb大小,需要优化。
6、测试打包后的js文件是否可以运行(OK)
搭建Vue3 SSR Server
依赖包:
- vue、vue-loader
- 安装:
pnpm i vue
- 安装:
pnpm i vue-loader -D
- 安装:
- babel-loader、@babel/preset-env
- 安装:
pnpm i babel-loader @babel/preset-env -D
- 安装:
搭建过程:
1、编写App.vue
2、配置解析后缀名
3、在src/app.js
中通过createSSRApp()
创建一个App实例。
4、在src/server/index.js
中通过renderToString()
方法将app实例转化为HTML字符串。并返回给前端
5、重命名wp.config.js
为server.config.js
并修改配置。
6、打包项目:pnpm run build:server
7、运行打包后的项目:pnpm run start
8、此时页面可以展示,但是不能互动,页面中的按钮不起作用。
Hydration
服务器端渲染页面 + 客户端激活页面,是页面有交互效果(这个过程称为:Hydration 水合)
Hydration的具体步骤如下:
1、在src/client/index.js
中通过createApp()
创建一个App实例并挂载到#app
元素上。
2、创建配置文件client.config.js
,并配置打包项。
3、创建打包脚本
4、打包项目:pnpm run build:client
5、在src/server/index.js
的HTML模板中,引入client_bundle.js
。JS文件部署在静态服务器中。
6、运行项目:pnpm run start
。此时页面中的JS代码已经激活。
问题:
解决:取消这2个常量的打包
合并配置
依赖包:
- webpack-merge:合并webpack配置。
- 安装:
pnpm i webpack-merge -D
- 安装:
base.config.js
server.config.js
client.config.js
const path = require('path')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base')
const { merge } = require('webpack-merge')
/**
* @type {import('webpack').Configuration}
*/
module.exports = merge(baseConfig, {
target: 'node',
entry: './src/server/index.js',
output: {
filename: 'server_bundle.js',
path: path.resolve(__dirname, '../build/server')
},
externals: [nodeExternals()]
})
const path = require('path')
const { DefinePlugin } = require('webpack')
const baseConfig = require('./webpack.base')
const { merge } = require('webpack-merge')
/**
* @type {import('webpack').Configuration}
*/
module.exports = merge(baseConfig, {
target: 'web',
entry: './src/client/index.js',
output: {
filename: 'client_bundle.js',
path: path.resolve(__dirname, '../build/client')
},
plugins: [
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
})
]
})
const { VueLoaderPlugin } = require('vue-loader')
/**
* @type {import('webpack').Configuration}
*/
module.exports = {
mode: 'development',
devtool: false,
output: {
clean: true
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.s[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
'@': path.resolve(__dirname, '../src')
},
extensions: ['.js', '.json', '.vue']
}
}
集成Vue Router
依赖包:
- vue-router
- 安装:
pnpm i vue-router
- 安装:
实现过程:
1、在src/router/index.js
中创建一个路由实例。
注意: 为了避免 跨请求状态污染,需在每个请求中都通过函数创建一个全新router。
2、在src/server/index.js
中挂载路由到app上。
3、在src/client/index.js
中也挂载一遍路由。
4、在src/App.vue
中添加路由占位。
5、效果
集成Pinia
依赖包:
- pinia
- 安装:
pnpm i pinia
- 安装:
实现过程:
1、在src/store/index.js
中创建一个pinia实例。
注意: 为了避免 跨请求状态污染,需在每个请求中都通过函数创建一个全新store。
2、在src/server/index.js
中挂载pinia到app上。
3、在src/client/index.js
中也挂载一遍pinia。
4、在组件中使用store
跨请求状态污染
跨请求状态污染(Cross-Request State Pollution)是指在 Web 应用中,多个请求之间的状态意外共享或干扰,导致数据不一致或安全问题。
产生原因:
在SPA环境中,每个用户在使用浏览器访问SPA应用时,应用模块都会重新初始化,这是一种单例模式。因此整个生命周期中只有一个App对象实例 或一个Router对象实例或一个Store对象实例。
在SSR环境中,App应用模块通常只在服务器启动时初始化一次。同一个应用模块会在多个服务器请求之间被复用,而我们的单例状态对象也一样,也会在多个请求之间被复用。当某个用户对共享的单例状态进行修改,那么这个状态可能会意外地泄露给另一个在请求的用户。
解决方案:
- 在每个请求中为整个App应用通过函数创建一个全新的实例,包括后面的 router 和全局 store等实例。
缺点:
- 需要消耗更多的服务器的资源,因为通过函数创建的实例都是保存在服务器端。